package com.example.backblog.controller.front.blog;

import com.example.backblog.annotation.RateLimiter;
import com.example.backblog.controller.base.BaseController;
import com.example.backblog.dto.blog.BlogDto;
import com.example.backblog.dto.blog.BlogNoContentDto;
import com.example.backblog.dto.blog.FullBlogDto;
import com.example.backblog.dto.blog.FullBlogWithoutContentDto;
import com.example.backblog.entity.blog.Blog;
import com.example.backblog.entity.blog.BlogTag;
import com.example.backblog.entity.blog.Category;
import com.example.backblog.entity.blog.Tag;
import com.example.backblog.enums.LimitType;
import com.example.backblog.read.blog.ReadBlogRepository;
import com.example.backblog.repository.blog.BlogRepository;
import com.example.backblog.repository.blog.BlogTagRepository;
import com.example.backblog.repository.blog.CategoryRepository;
import com.example.backblog.repository.blog.TagRepository;
import com.example.backblog.service.blog.CategoryService;
import com.example.backblog.service.blog.DistributedLockService;
import com.example.backblog.service.blog.TagService;
import com.example.backblog.vo.ApiResult;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.transaction.Transactional;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@RestController()
@RequestMapping("/blog")
public class BlogController extends BaseController<Blog, BlogDto, BlogRepository> {
    @Autowired
    private CategoryRepository categoryRepository;
    @Autowired
    private TagRepository tagRepository;
    @Autowired
    private TagService tagService;
    @Autowired
    private CategoryService categoryService;
    @Autowired
    private BlogTagRepository blogTagRepository;
    @Autowired
    private RestHighLevelClient esClient;
    @Autowired
    private DistributedLockService distributedLockService;
    @Autowired
    private ReadBlogRepository readBlogRepository;
    @Autowired
    @Override
    public void setBaseRepository(BlogRepository baseRepository) {
        this.baseRepository = baseRepository;
    }
//    @RateLimiter(key = "bloglist",limitType = LimitType.IP)
    @GetMapping("/full")
    public ApiResult listBlog(Pageable pageable){
        Page<Blog> all = this.readBlogRepository.findAll(pageable);
        List<Blog> allBlog = all.getContent();
        List<Tag> allTag = tagService.listAll();
        List<Category> allCategory = categoryService.listAll();
        Map<Long,Tag> id2Tag = allTag.stream().collect(Collectors.toMap(Tag::getId,(obj)->obj));
        Map<Long,Category> id2Category = allCategory.stream().collect(Collectors.toMap(Category::getId,(obj)->obj));
        List<FullBlogDto> fullBlogDtos = new ArrayList<>();
        for (Blog blog : allBlog) {
            FullBlogDto fullBlogDto = new FullBlogDto();
            BeanUtils.copyProperties(blog,fullBlogDto);
            fullBlogDto.setCategory(id2Category.get(blog.getCategoryId()));
            List<BlogTag> allBlogTag = blogTagRepository.findAllByBlogIdEquals(blog.getId());
            List<Tag> tags = new ArrayList<>();
            allBlogTag.stream().forEach(blogTag -> tags.add(id2Tag.get(blogTag.getTagId())));
            fullBlogDto.setTags(tags);
            fullBlogDtos.add(fullBlogDto);
        }
        HashMap<String, Object> res = new HashMap<>();
        res.put("postList",fullBlogDtos);
        res.put("pages",all.getTotalPages());
        return ApiResult.success(res);

    }
    @GetMapping("/full/categoryOrTag")
    public ApiResult listBlogByCategoryAndTag(String category,String tag, Boolean shortContent, Pageable pageable){
        if (shortContent == null) {
            shortContent = true;
        }
        boolean categoryEmpty = StringUtils.isEmpty(category);
        boolean tagEmpty = StringUtils.isEmpty(tag);
        Page<Blog> all = null;
        if(categoryEmpty&&tagEmpty){
            all = this.readBlogRepository.findAll(pageable);
        }else if(categoryEmpty){
            Tag queryTag = tagRepository.findFirstByNameEquals(tag);
            List<BlogTag> allBlogTags = blogTagRepository.findAllByTagIdEquals(queryTag.getId());
            List<Long> blogIds = allBlogTags.stream().map(BlogTag::getBlogId).collect(Collectors.toList());
            all = this.readBlogRepository.findAllByIdIn(blogIds, pageable);
        }else if(tagEmpty){
            Category queryCategory = categoryRepository.findFirstByNameEquals(category);
            if (queryCategory == null) {
                HashMap<String, Object> res = new HashMap<>();
                res.put("postList",new ArrayList<>());
                res.put("pages",0);
                return ApiResult.success(res);
            }
            all = this.readBlogRepository.findAllByCategoryIdEquals(queryCategory.getId(),pageable);
        }else{

        }
        List<Blog> allBlog = all.getContent();
        List<Tag> allTag = tagService.listAll();
        List<Category> allCategory = categoryService.listAll();
        Map<Long,Tag> id2Tag = allTag.stream().collect(Collectors.toMap(Tag::getId,(obj)->obj));
        Map<Long,Category> id2Category = allCategory.stream().collect(Collectors.toMap(Category::getId,(obj)->obj));
        List<FullBlogDto> fullBlogDtos = new ArrayList<>();
        for (Blog blog : allBlog) {
            FullBlogDto fullBlogDto = new FullBlogDto();
            BeanUtils.copyProperties(blog,fullBlogDto);
            fullBlogDto.setCategory(id2Category.get(blog.getCategoryId()));
            List<BlogTag> allBlogTag = blogTagRepository.findAllByBlogIdEquals(blog.getId());
            List<Tag> tags = new ArrayList<>();
            allBlogTag.stream().forEach(blogTag -> tags.add(id2Tag.get(blogTag.getTagId())));
            fullBlogDto.setTags(tags);
            fullBlogDtos.add(fullBlogDto);
        }
        if (!shortContent) {
            fullBlogDtos.stream().forEach((post)->{
                if (post.getContent() != null) {
                    int size = post.getContent().length()>=20?20:post.getContent().length();
                    post.setContent(post.getContent().substring(0,size));
                  }
                }

            );
        }
        HashMap<String, Object> res = new HashMap<>();
        res.put("postList",fullBlogDtos);
        res.put("pages",all.getTotalPages());
        return ApiResult.success(res);

    }
    @GetMapping("/fullWithoutContent")
    public ApiResult listBlogWithoutContent(Pageable pageable){
        Page<Blog> all = this.readBlogRepository.findAll(pageable);
        List<Blog> allBlog = all.getContent();
        List<Tag> allTag = tagService.listAll();
        List<Category> allCategory = categoryService.listAll();
        Map<Long,Tag> id2Tag = allTag.stream().collect(Collectors.toMap(Tag::getId,(obj)->obj));
        Map<Long,Category> id2Category = allCategory.stream().collect(Collectors.toMap(Category::getId,(obj)->obj));
        List<FullBlogWithoutContentDto> fullBlogDtos = new ArrayList<>();
        for (Blog blog : allBlog) {
            FullBlogWithoutContentDto fullBlogDto = new FullBlogWithoutContentDto();
            BeanUtils.copyProperties(blog,fullBlogDto);
            fullBlogDto.setCategory(id2Category.get(blog.getCategoryId()));
            List<BlogTag> allBlogTag = blogTagRepository.findAllByBlogIdEquals(blog.getId());
            List<Tag> tags = new ArrayList<>();
            allBlogTag.stream().forEach(blogTag -> tags.add(id2Tag.get(blogTag.getTagId())));
            fullBlogDto.setTags(tags);
            fullBlogDtos.add(fullBlogDto);
        }
        HashMap<String, Object> res = new HashMap<>();
        res.put("postList",fullBlogDtos);
        res.put("pages",all.getTotalPages());
        return ApiResult.success(res);

    }
    @GetMapping("/full/one")
    public ApiResult oneFullBlog(Long id){
        Optional<Blog> byId = this.readBlogRepository.findById(id);
        Blog blog = byId.get();
        List<Tag> allTag = tagService.listAll();
        List<Category> allCategory = categoryService.listAll();
        Map<Long,Tag> id2Tag = allTag.stream().collect(Collectors.toMap(Tag::getId,(obj)->obj));
        Map<Long,Category> id2Category = allCategory.stream().collect(Collectors.toMap(Category::getId,(obj)->obj));
        FullBlogDto fullBlogDto = new FullBlogDto();
        BeanUtils.copyProperties(blog,fullBlogDto);
        fullBlogDto.setCategory(id2Category.get(blog.getCategoryId()));
        List<BlogTag> allBlogTag = blogTagRepository.findAllByBlogIdEquals(blog.getId());
        List<Tag> tags = new ArrayList<>();
        allBlogTag.stream().forEach(blogTag -> tags.add(id2Tag.get(blogTag.getTagId())));
        fullBlogDto.setTags(tags);
        return ApiResult.success(fullBlogDto);

    }
    @GetMapping("/full/like")
    public ApiResult listBlogLike(String title, Boolean shortContent, Pageable pageable){
        Page<Blog> all = this.readBlogRepository.findAllByTitleLike("%"+title+"%",pageable);
        List<FullBlogDto> fullBlogDtos = getFullBlogDtos(shortContent, all);
        HashMap<String, Object> res = new HashMap<>();
        res.put("postList",fullBlogDtos);
        res.put("pages",all.getTotalPages());
        return ApiResult.success(res);
    }

    @NotNull
    private List<FullBlogDto> getFullBlogDtos(Boolean shortContent, Page<Blog> all) {
        List<Blog> allBlog = all.getContent();
        List<Tag> allTag = tagService.listAll();
        List<Category> allCategory = categoryService.listAll();
        Map<Long,Tag> id2Tag = allTag.stream().collect(Collectors.toMap(Tag::getId,(obj)->obj));
        Map<Long,Category> id2Category = allCategory.stream().collect(Collectors.toMap(Category::getId,(obj)->obj));
        List<FullBlogDto> fullBlogDtos = new ArrayList<>();
        for (Blog blog : allBlog) {
            FullBlogDto fullBlogDto = new FullBlogDto();
            BeanUtils.copyProperties(blog,fullBlogDto);
            fullBlogDto.setCategory(id2Category.get(blog.getCategoryId()));
            List<BlogTag> allBlogTag = blogTagRepository.findAllByBlogIdEquals(blog.getId());
            List<Tag> tags = new ArrayList<>();

            allBlogTag.stream().forEach(blogTag -> tags.add(id2Tag.get(blogTag.getTagId())));
            fullBlogDto.setTags(tags);

            // 缩短文章

            if(shortContent !=null && shortContent ==true){
                int length = Math.min(20,fullBlogDto.getContent().length());
                fullBlogDto.setContent(fullBlogDto.getContent().substring(0,length));
            }
            fullBlogDtos.add(fullBlogDto);
        }
        return fullBlogDtos;
    }

    @GetMapping("/full/like/v2")
    public ApiResult listBlogES(String title,Boolean shortContent,Pageable pageable) throws IOException {
        SearchRequest request = new SearchRequest();

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.matchQuery("title", title));

        // 添加高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<em>").postTags("</em>");
        sourceBuilder.highlighter(highlightBuilder);

        // 排序
        sourceBuilder.sort(SortBuilders.fieldSort("_score").order(SortOrder.DESC));
        request.source(sourceBuilder);
        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
        SearchHits hits = response.getHits();
        SearchHit[] total = hits.getHits();
        List<Long> ids = new ArrayList<>();
        for (SearchHit documentFields : total) {
            ids.add(Long.valueOf(documentFields.getId()));
        }

        Page<Blog> all = this.readBlogRepository.findAllByIdIn(ids,pageable);

        List<FullBlogDto> fullBlogDtos = getFullBlogDtos(shortContent, all);
        HashMap<String, Object> res = new HashMap<>();
        res.put("postList",fullBlogDtos);
        res.put("pages",all.getTotalPages());
        return ApiResult.success(res);
    }
    @GetMapping("/hot")
    public ApiResult listHotBlog(){
        Page<Blog> allByOrderByViewsDesc = this.readBlogRepository.findAllByOrderByViewsDesc(Pageable.ofSize(10));
        List<Blog> content = allByOrderByViewsDesc.getContent();
        List<BlogNoContentDto> blogNoContentDtos = new ArrayList<>();
        for (Blog blog : content) {
            BlogNoContentDto blogNoContentDto = new BlogNoContentDto();
            BeanUtils.copyProperties(blog,blogNoContentDto);
            blogNoContentDtos.add(blogNoContentDto);
        }
        return ApiResult.success(blogNoContentDtos);
    }
    @GetMapping("/top")
    public ApiResult topOne(){
        Blog topBlog = this.readBlogRepository.findFirstByTopEquals(true);
        Long categoryId = topBlog.getCategoryId();
        FullBlogDto fullBlogDto = new FullBlogDto();
        BeanUtils.copyProperties(topBlog,fullBlogDto);
        if (categoryId != null) {
            Optional<Category> byId = categoryRepository.findById(categoryId);
            fullBlogDto.setCategory(byId.get());

        }
        List<BlogTag> allByBlogIdEquals = blogTagRepository.findAllByBlogIdEquals(topBlog.getId());
        List<Long> collect = allByBlogIdEquals.stream().map(BlogTag::getTagId).collect(Collectors.toList());
        List<Tag> allByIdIn = tagRepository.findAllByIdIn(collect);
        fullBlogDto.setTags(allByIdIn);
        return ApiResult.success(fullBlogDto);
    }
    @PutMapping("/view")
    @Transactional
    public ApiResult plusView(Long id) throws Exception {
        boolean lock = distributedLockService.acquireLock("view" + id, 5000);
        if(!lock){
            return ApiResult.error();
        }
        Optional<Blog> byId = this.baseRepository.findById(id);
        if (byId.isPresent()) {
            Blog blog = byId.get();
            long view = 0;
            if (blog.getViews()!=null) {
                view = blog.getViews();
            }
            blog.setViews(view+1);
            this.baseRepository.save(blog);
            distributedLockService.releaseLock("view"+id);
            return ApiResult.success();
        }else {
            distributedLockService.releaseLock("view"+id);
            throw new Exception("文章不存在");
        }


    }
}
